var fs = require('fs');
var config = JSON.parse(fs.readFileSync("config.json", "utf-8"));

if (config.protocol == "https") {
    var options = {
        key: fs.readFileSync(config.key),
        cert: fs.readFileSync(config.cert)
    };
    var https = require('https');
    var app = https.createServer(options);
} else {
    var http = require('http');
    var app = http.createServer();
}

var io = require('socket.io').listen(app);
app.listen(config.port, '0.0.0.0');

var ss = require('socket.io-stream');
var path = require('path');
io.set('transports', ['websocket']);
var mysql = require("mysql");
var pool = mysql.createPool(config.mysqlPool);
var async = require('async');

//在线用户
var onlineUsers = [];
//当前在线人数
var onlineCount = 0;

if (config.appName == "expert") {
    var _assignation_cursor = 0;

    function _findCustomerAgent(result) {
        // find online agents
        var online_agents = [];
        var j = 0;
        for (var i = 0; i < result.length; i++) {
            if (!!onlineUsers[result[i].uid]) {
                online_agents[j++] = result[i].uid;
            }
        }
        if (j == 0) {
            return result[(_assignation_cursor++) % result.length].uid;
        } else {
            return online_agents[(_assignation_cursor++) % j];
        }
    }

    function assignAgentCustomer(socket, data, callback) {
        var conn = this.conn;

        var agent_uid = null;
        var isHistoryAgentOnline = false;
        var newAgentInHistory = 1;

        var timestamp = new Date().getTime();

        // 查找历史会话
        conn.query('SELECT * from chat WHERE chat_id = ' + data.chatId, function (err, chatExist) {
            if (err) {
                return callback(new Error('查找chat表失败' + err));
            }
            if (!chatExist || chatExist.length == 0) {
                async.waterfall([
                    function (callback) {
                        conn.query("SELECT uid FROM users_roles where rid = 4", function (err, result) {
                            if (err) {
                                return callback(new Error('取得潜在客服SQL错误' + err));
                            }
                            if (!result || result.length == 0) {
                                return callback(new Error('客服暂时不可用！'));
                            }
                            agent_uid = _findCustomerAgent(result);
                            console.log('AssignAG-新会话 | 1.分配客服：' + agent_uid);
                            callback();
                        });
                    },
                    function (callback) {
                        console.log('AssignAG-新会话 | 2.建立会话');
                        conn.query('INSERT INTO chat (chat_id, title, timestamp, type, active) VALUES (' + data.chatId + ',"' + data.chatName + '",' + timestamp + ',' + config.chatTypeMix + ',' + 1 + ')', function (err, insertChatResult) {
                            if (err) {
                                return callback(new Error('插入chat表失败' + err));
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        console.log('AssignAG-新会话 | 3.插入客户到chat_users');
                        var insertChatCustomer = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' +
                            data.chatId + ',' + socket.uid + ',"' + '客户[' + socket.uid + ']",' + 1 + ',' + 1 + ',' + timestamp + ')';
                        conn.query(insertChatCustomer, function (err, insertResult) {
                            if (err) {
                                return callback(new Error('插入chat_users表失败-客户' + err));
                            }
                            callback();
                        });

                    },
                    function (callback) {
                        console.log('AssignAG-新会话 | 4.插入客服到chat_users');
                        var insertChatAgent = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' +
                            data.chatId + ',' + agent_uid + ',"' + "专家客服" + '",' + 1 + ',' + 1 + ',' + 0 + ')';
                        conn.query(insertChatAgent, function (err, insertResult) {
                            if (err) {
                                return callback(new Error('插入chat_users表失败-客服' + err));
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        console.log('AssignAG-新会话 | 5.发送给客户对话列表');
                        getChatsCommon.apply({conn: conn}, [socket, null, function (err, result) {
                            if (err) {
                                return callback(new Error('发送给客户对话列表失败' + err));
                            }
                            callback();
                        }]);
                    },
                    function (callback) {
                        console.log('AssignAG-新会话 | 6.推送客服新对话');
                        if (onlineUsers[agent_uid]) {
                            getChatsCommon.apply({conn: conn}, [onlineUsers[agent_uid], data.chatId, function (err, result) {
                                if (err) {
                                    return callback(new Error('发送给客户新的对话失败' + err));
                                }
                                callback();
                            }]);
                        }
                    }
                ], function (err, result) {
                    if (err) {
                        return callback(err);
                    }
                    return callback();
                });
            }

            if (chatExist.length > 0) {
                async.waterfall([
                    function (callback) {
                        console.log('AssignAG-历史会话 | 1.查找历史客服');
                        conn.query('SELECT * from chat_users WHERE chat_id = ' + data.chatId + ' AND active = 1 AND user_id != ' + socket.uid, function (err, result) {
                            if (err) {
                                return callback(new Error('查找历史客服失败' + err));
                            }
                            if (result.length > 0) {
                                var agentCandidate = result[0].user_id;
                                if (onlineUsers[agentCandidate]) {
                                    //历史客服在线，继续使用
                                    agent_uid = agentCandidate;
                                    isHistoryAgentOnline = true;
                                } else if (timestamp - result[0].checkpoint < config.assignAgentGap) {
                                    //如果该客服assignAgentGap时间前还在线回复过，就不重新分配客服
                                    agent_uid = agentCandidate;
                                    isHistoryAgentOnline = true;
                                }
                            } else {
                                console.log('chat_users表错误：chat_id = ' + data.chatId + ' 同时active客服=' + result.length);
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            conn.query("SELECT uid FROM users_roles where rid = 4", function (err, result) {
                                if (err) {
                                    return callback(new Error('取得潜在客服SQL错误' + err));
                                }
                                if (!result || result.length == 0) {
                                    return callback(new Error('客服暂时不可用！'));
                                }
                                agent_uid = _findCustomerAgent(result);
                                console.log('AssignAG-历史会话 | 2.重新分配客服：' + agent_uid);
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            console.log('AssignAG-历史会话 | 3.重新分配客服 - 禁止原客服');
                            conn.query('UPDATE chat_users SET active = 0 WHERE chat_id = ' + data.chatId + ' AND user_id != ' + socket.uid, function (err, updateHistoryChat) {
                                if (err) {
                                    return callback(new Error('禁止原客服失败' + err));
                                }
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            console.log('AssignAG-历史会话 | 4.重新分配客服 - 新客服已在会话中');
                            conn.query('UPDATE chat_users SET active = 1 WHERE chat_id = ' + data.chatId + ' AND user_id = ' + agent_uid, function (err, updateAgentChat) {
                                if (err) {
                                    return callback(new Error('更新新客服会话失败' + err));
                                }
                                newAgentInHistory = updateAgentChat.affectedRows;
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline && newAgentInHistory == 0) {
                            console.log('AssignAG-历史会话 | 5.重新分配客服 - 新客服不在会话中，需要插入');
                            var insertChatAgent = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' +
                                data.chatId + ',' + agent_uid + ',"' + "专家客服" + '",' + 1 + ',' + 1 + ',' + 0 + ')';
                            conn.query(insertChatAgent, function (err, insertResult) {
                                if (err) {
                                    return callback(new Error('插入chat_users表失败-客服' + err));
                                }
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        console.log('AssignAG-历史会话 | 6.发送客户对话列表');
                        //5.发送给客户新的对话列表
                        getChatsCommon.apply({conn: conn}, [socket, null, function (err, result) {
                            if (err) {
                                return callback(new Error('发送客户对话列表失败' + err));
                            }
                            callback();
                        }]);
                    },
                    function (callback) {
                        var agent_socket = onlineUsers[agent_uid];
                        if (!agent_socket) {
                            //客服不在线 || 客服突然下线
                            console.log('AssignAG-历史会话 | 7.更新客服对话列表 - 客服不在线无需更新');
                            callback();
                        } else {
                            if (!isHistoryAgentOnline) {
                                console.log('AssignAG-历史会话 | 7.更新客服对话列表');
                                getChatsCommon.apply({conn: conn}, [agent_socket, data.chatId, function (err, result) {
                                    if (err) {
                                        return callback(new Error('发送新客服对话列表失败' + err));
                                    }
                                    callback();
                                }]);
                            } else {
                                console.log('AssignAG-历史会话 | 7.更新客服对话列表 - 客服未更换无需更新');
                                callback();
                            }
                        }
                    }
                ], function (err, result) {
                    if (err) {
                        return callback(err);
                    }
                    return callback();
                });
            }
        });
    }

}

if (config.appName == "nonghua") {
//获取咨询/投诉用SQL
    var getConsultComplainSQL = 'select OA0.uid as a0_uid, OA0.alias as a0_alias, ' +
        '	OA1.uid as a1_uid, OA1.alias as a1_alias,  ' +
        '	OA2.uid as a2_uid, OA2.alias as a2_alias,  ' +
        '	OA3.uid as a3_uid, OA3.alias as a3_alias, ' +
        '	OA4.uid as a4_uid, OA4.alias as a4_alias,  ' +
        '	OA5.uid as a5_uid, OA5.alias as a5_alias from ' +
        '	(select A0.addr_id as a0_id, A1.addr_id as a1_id, A2.addr_id as a2_id, A3.addr_id as a3_id, A4.addr_id as a4_id, A5.addr_id as a5_id from  ' +
        '		(select addr.* from farmer F join addr on F.addr_id = addr.addr_id where F.farmer_id = @farmer_id) A  ' +
        '	left join addr A0 on A.country_id = A0.addr_id ' +
        '	left join addr A1 on A.province_id = A1.addr_id  ' +
        '	left join addr A2 on A.city_id = A2.addr_id ' +
        '	left join addr A3 on A.county_id = A3.addr_id ' +
        '	left join addr A4 on A.town_id = A4.addr_id ' +
        '	left join addr A5 on A.village_id = A5.addr_id) AGENTS ' +
        'left join online_agent OA0 on AGENTS.a0_id = OA0.addr_id and OA0.chat_group_id = @type ' +
        'left join online_agent OA1 on AGENTS.a1_id = OA1.addr_id and OA1.chat_group_id = @type ' +
        'left join online_agent OA2 on AGENTS.a2_id = OA2.addr_id and OA2.chat_group_id = @type ' +
        'left join online_agent OA3 on AGENTS.a3_id = OA3.addr_id and OA3.chat_group_id = @type ' +
        'left join online_agent OA4 on AGENTS.a4_id = OA4.addr_id and OA4.chat_group_id = @type ' +
        'left join online_agent OA5 on AGENTS.a5_id = OA5.addr_id and OA5.chat_group_id = @type';

    function _findAgent(result) {
        var agent_uid = null;
        var agent_alias = null;

        if (result.length > 0) {
            var a0_uids = [];
            var a1_uids = [];
            var a2_uids = [];
            var a3_uids = [];
            var a4_uids = [];
            var a5_uids = [];

            //找出所有潜在的客服
            for (var i = 0; i < result.length; i++) {
                if (result[i].a0_uid) {
                    a0_uids[result[i].a0_uid] = result[i].a0_alias;
                }
                if (result[i].a1_uid) {
                    a1_uids[result[i].a1_uid] = result[i].a1_alias;
                }
                if (result[i].a2_uid) {
                    a2_uids[result[i].a2_uid] = result[i].a2_alias;
                }
                if (result[i].a3_uid) {
                    a3_uids[result[i].a3_uid] = result[i].a3_alias;
                }
                if (result[i].a4_uid) {
                    a4_uids[result[i].a4_uid] = result[i].a4_alias;
                }
                if (result[i].a5_uid) {
                    a5_uids[result[i].a5_uid] = result[i].a5_alias;
                }
            }

            //试着找到在线客服
            if (agent_uid == null) {
                for (var key in a5_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a5_uids[key];
                        break;
                    }
                }
            }
            if (agent_uid == null) {
                for (var key in a4_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a4_uids[key];
                        break;
                    }
                }
            }
            if (agent_uid == null) {
                for (var key in a3_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a3_uids[key];
                        break;
                    }
                }
            }
            if (agent_uid == null) {
                for (var key in a2_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a2_uids[key];
                        break;
                    }
                }
            }
            if (agent_uid == null) {
                for (var key in a1_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a1_uids[key];
                        break;
                    }
                }
            }
            if (agent_uid == null) {
                for (var key in a0_uids) {
                    if (!!onlineUsers[key]) {
                        agent_uid = key;
                        agent_alias = a0_uids[i];
                        break;
                    }
                }
            }

            //如果无任何在线客服，找到不在线客服地理位置最精确的
            if (agent_uid == null) {
                for (var key in a5_uids) {
                    agent_uid = key;
                    agent_alias = a5_uids[key];
                    break;
                }
            }
            if (agent_uid == null) {
                for (var key in a4_uids) {
                    agent_uid = key;
                    agent_alias = a4_uids[key];
                    break;
                }
            }
            if (agent_uid == null) {
                for (var key in a3_uids) {
                    agent_uid = key;
                    agent_alias = a3_uids[key];
                    break;
                }
            }
            if (agent_uid == null) {
                for (var key in a2_uids) {
                    agent_uid = key;
                    agent_alias = a2_uids[key];
                    break;
                }
            }
            if (agent_uid == null) {
                for (var key in a1_uids) {
                    agent_uid = key;
                    agent_alias = a1_uids[key];
                    break;
                }
            }
            if (agent_uid == null) {
                for (var key in a0_uids) {
                    agent_uid = key;
                    agent_alias = a0_uids[i];
                    break;
                }
            }
        }

        var returnVal = {};
        returnVal.agent_uid = agent_uid;
        returnVal.agent_alias = agent_alias;

        return returnVal;
    }

    function assignAgentCommon(socket, type, offset, callback) {
        var farmer_uid = socket.uid;
        var conn = this.conn;

        var agent_uid = null;
        var agent_alias = null;
        var isHistoryAgentOnline = false;
        var newAgentInHistory = 1;

        var timestamp = new Date().getTime();

        //农户的chat_id介于1~10000000之间，理论上支持500万农户。农户的chat_id使用农户uid*chatRange+offset来实现(即：咨询为uid*2+0,投诉为uid*2+1)
        var farmerChatId = farmer_uid * config.chatRange + offset;

        conn.query('SELECT * from chat WHERE chat_id = ' + farmerChatId + ' ', function (err, chatExist) {
            if (err) {
                return callback(new Error('查找chat表失败' + err));
            }
            if (!chatExist || chatExist.length == 0) {
                console.log('AssignAG-New');
                async.waterfall([
                    function (callback) {
                        //1.查找在线客服
                        console.log('1.查找在线客服');
                        var sql = getConsultComplainSQL.replace(new RegExp("@farmer_id", "g"), farmer_uid).replace(new RegExp("@type", "g"), type);
                        conn.query(sql, function (err, result) {
                            console.log(sql);
                            if (err) {
                                return callback(new Error('取得潜在客服SQL错误' + err));
                            }
                            if (!result || result.length == 0) {
                                return callback(new Error('客服暂时不可用！'));
                            }
                            var returnVal = _findAgent(result);
                            agent_uid = returnVal.agent_uid;
                            agent_alias = returnVal.agent_alias;

                            if (agent_uid == null) {
                                return callback(new Error('所在区未分配客服，请通过其他方式联系我们'));
                            }

                            callback();
                        });
                    },
                    function (callback) {
                        //2.不存在历史会话，建立会话
                        console.log('2.不存在历史会话，建立会话');
                        conn.query('INSERT INTO chat (chat_id, title, timestamp, type, active) VALUES (' + farmerChatId + ',"' + agent_uid + '&' + farmer_uid + '",' + timestamp + ',' + config.chatTypeMix + ',' + 1 + ')', function (err, insertChatResult) {
                            if (err) {
                                return callback(new Error('插入chat表失败' + err));
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        //3.插入农户到chat_users
                        console.log('插入农户到chat_users');
                        var insertChatFarmer = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' + farmerChatId + ',' + farmer_uid + ',' +
                            '(select farmer_name from farmer where farmer_id = ' + farmer_uid + '),' + type + ',' + 1 + ',' + timestamp + ')';
                        conn.query(insertChatFarmer, function (err, insertFarmer) {
                            if (err) {
                                return callback(new Error('插入chat_users表失败-农户' + err));
                            }
                            callback();
                        });

                    },
                    function (callback) {
                        //4.插入客服到chat_users
                        console.log('插入客服到chat_users');
                        var insertChatAgent = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' + farmerChatId + ',' + agent_uid + ',"' + agent_alias + '",' + type + ',' + 1 + ',' + 0 + ')';
                        conn.query(insertChatAgent, function (err, insertFarmer) {
                            if (err) {
                                return callback(new Error('插入chat_users表失败-客服' + err));
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        //5.发送给农户新的对话列表
                        console.log('发送给农户新的对话列表');
                        getChatsCommon.apply({conn: conn}, [socket, null, function (err, result) {
                            if (err) {
                                return callback(err);
                            }
                            callback();
                        }]);
                    },
                    function (callback) {
                        //6.更新客服列表结构
                        var agent_socket = onlineUsers[agent_uid];

                        if (!agent_socket) {
                            //客服不在线 || 客服突然下线
                            console.log('无须更新客服列表结构-客服不在线');
                            //造一条以客服名义的消息发送给农户
                            receiveMessage.apply({conn: conn}, [
                                    {'uid': agent_uid},
                                    {
                                        'chat_id': farmerChatId,
                                        'chat_type': config.chatTypeMix,
                                        'content': config.offlineWelcome
                                    }, null,
                                    function (err, result) {
                                        if (err) {
                                            return callback(err);
                                        }
                                        console.log('<<<第一次分配离线客服成功[AgentId:' + agent_uid + '][AgentAlias:' + agent_alias + ']');
                                        callback();
                                    }]
                            );

                        } else {
                            console.log('更新客服列表结构');
                            getChatsCommon.apply({conn: conn}, [agent_socket, farmerChatId, function (err, result) {
                                if (err) {
                                    return callback(err);
                                }
                                //造一条以客服名义的消息发送给农户
                                receiveMessage.apply({conn: conn}, [
                                        {'uid': agent_uid},
                                        {
                                            'chat_id': farmerChatId,
                                            'chat_type': config.chatTypeMix,
                                            'content': config.onlineWelcome
                                        }, null,
                                        function (err, result) {
                                            if (err) {
                                                return callback(err);
                                            }
                                            console.log('<<<第一次分配在线客服成功[AgentId:' + agent_uid + '][AgentAlias:' + agent_alias + ']');
                                            callback();
                                        }]
                                );
                            }]);
                        }
                    }
                ], function (err, result) {
                    if (err) {
                        return callback(err);
                    }
                    return callback();
                });
            }

            if (chatExist.length > 0) {
                console.log('AssignAG*-Existing');
                async.waterfall([
                    function (callback) {
                        //1.更新农户姓名
                        console.log('更新农户姓名');
                        conn.query('UPDATE chat_users SET nickname = (select farmer_name from farmer where farmer_id = ' + farmer_uid + ') WHERE chat_id = ' + farmerChatId + ' AND user_id = ' + farmer_uid, function (err, updateHistoryChat) {
                            if (err) {
                                return callback(new Error('更新农户姓名失败' + err));
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        //2.看历史客服是否在线，如果在线，继续用此客服。如果没有，进入下一步找在线客服。
                        console.log('看历史客服是否在线，如果在线，继续用此客服');
                        conn.query('SELECT * from chat_users WHERE chat_id = ' + farmerChatId + ' AND active = 1 AND user_id != ' + farmer_uid, function (err, result) {
                            if (err) {
                                return callback(new Error('查找历史活跃客服失败' + err));
                            }
                            if (result.length > 0) {
                                var agentCandidate = result[0].user_id;
                                if (onlineUsers[agentCandidate]) {
                                    //历史客服在线，继续使用
                                    agent_uid = agentCandidate;
                                    isHistoryAgentOnline = true;
                                } else if (timestamp - result[0].checkpoint < config.assignAgentGap) {
                                    //如果该客服assignAgentGap时间前还在线回复过，就不重新分配客服
                                    agent_uid = agentCandidate;
                                    isHistoryAgentOnline = true;
                                }
                            } else {
                                console.log('chat_users表错误：chat_id = ' + data.chatId + ' 同时active客服=' + result.length);
                            }
                            callback();
                        });
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            //3.查找在线客服
                            console.log('查找在线客服');
                            var sql = getConsultComplainSQL.replace(new RegExp("@farmer_id", "g"), farmer_uid).replace(new RegExp("@type", "g"), type);
                            conn.query(sql, function (err, result) {
                                console.log(sql);
                                if (err) {
                                    return callback(new Error('取得潜在客服SQL错误' + err));
                                }
                                if (!result || result.length == 0) {
                                    return callback(new Error('客服暂时不可用！'));
                                }
                                var returnVal = _findAgent(result);
                                agent_uid = returnVal.agent_uid;
                                agent_alias = returnVal.agent_alias;

                                if (agent_uid == null) {
                                    return callback(new Error('无在线客服，请通过其他方式联系我们'));
                                }

                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            //4.禁止所有该会话下非农户的chat_users。
                            console.log('禁止所有该会话下非农户的chat_users。');
                            conn.query('UPDATE chat_users SET active = 0 WHERE chat_id = ' + farmerChatId + ' AND user_id != ' + farmer_uid, function (err, updateHistoryChat) {
                                if (err) {
                                    return callback(new Error('禁止原客服失败' + err));
                                }
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline) {
                            console.log('试着激活新客服');
                            conn.query('UPDATE chat_users SET active = 1 WHERE chat_id = ' + farmerChatId + ' AND user_id = ' + agent_uid, function (err, updateAgentChat) {
                                if (err) {
                                    return callback(new Error('更新新客服会话失败' + err));
                                }
                                newAgentInHistory = updateAgentChat.affectedRows;
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        if (!isHistoryAgentOnline && newAgentInHistory == 0) {
                            //6.当前客服不在历史客服中，插入客服
                            console.log('插入新客服');
                            var insertChatAgent = 'INSERT INTO chat_users (chat_id, user_id, nickname, chat_group_id, active, checkpoint) ' + ' VALUES (' + farmerChatId + ',' + agent_uid + ',"' + agent_alias + '",' + type + ',' + 1 + ',' + 0 + ')';
                            conn.query(insertChatAgent, function (err, insertFarmer) {
                                if (err) {
                                    return callback(new Error('插入chat_users表失败-客服' + err));
                                }
                                callback();
                            });
                        } else {
                            callback();
                        }
                    },
                    function (callback) {
                        //7.发送给农户新的对话列表
                        console.log('发送给农户新的对话列表');
                        getChatsCommon.apply({conn: conn}, [socket, null, function (err, result) {
                            if (err) {
                                return callback(err);
                            }
                            callback();
                        }]);
                    },
                    function (callback) {
                        var agent_socket = onlineUsers[agent_uid];
                        if (!agent_socket) {
                            //客服不在线 || 客服突然下线
                            console.log('无须更新客服列表结构-客服不在线');
                            //造一条以客服名义的消息发送给农户
                            receiveMessage.apply({conn: conn}, [
                                    {'uid': agent_uid},
                                    {
                                        'chat_id': farmerChatId,
                                        'chat_type': config.chatTypeMix,
                                        'content': config.offlineWelcome
                                    }, null,
                                    function (err, result) {
                                        if (err) {
                                            return callback(err);
                                        }
                                        console.log('<<<分配离线客服成功[AgentId:' + agent_uid + '][AgentAlias:' + agent_alias + ']');
                                        callback();
                                    }]
                            );
                        } else {
                            if (!isHistoryAgentOnline) {
                                //8.更新客服列表结构
                                console.log('更新客服列表结构');
                                getChatsCommon.apply({conn: conn}, [agent_socket, farmerChatId, function (err, result) {
                                    if (err) {
                                        return callback(err);
                                    }
                                    //造一条以客服名义的消息发送给农户
                                    receiveMessage.apply({conn: conn}, [
                                            {'uid': agent_uid},
                                            {
                                                'chat_id': farmerChatId,
                                                'chat_type': config.chatTypeMix,
                                                'content': config.onlineWelcome
                                            }, null,
                                            function (err, result) {
                                                if (err) {
                                                    return callback(err);
                                                }
                                                console.log('<<<分配新的在线客服成功[AgentId:' + agent_uid + '][AgentAlias:' + agent_alias + ']');
                                                callback();
                                            }]
                                    );
                                }]);
                            } else {
                                //造一条以客服名义的消息发送给农户
                                receiveMessage.apply({conn: conn}, [
                                        {'uid': agent_uid},
                                        {
                                            'chat_id': farmerChatId,
                                            'chat_type': config.chatTypeMix,
                                            'content': config.onlineWelcome
                                        }, null,
                                        function (err, result) {
                                            if (err) {
                                                return callback(err);
                                            }
                                            console.log('<<<分配历史在线客服成功[AgentId:' + agent_uid + '][AgentAlias:' + agent_alias + ']');
                                            callback();
                                        }]
                                );
                            }
                        }
                    }
                ], function (err, result) {
                    if (err) {
                        return callback(err);
                    }
                    return callback();
                });
            }
        });
    }
}


function parseCookies(cookie) {
    if (!cookie) {
        return;
    }
    return cookie.split(';').reduce(
        function (prev, curr) {
            var m = / *([^=]+)=(.*)/.exec(curr);
            var key = m[1];
            var value = decodeURIComponent(m[2]);
            prev[key] = value;
            return prev;
        },
        {}
    );
}

function _loopGetChatUsersByIds(conn, chat_ids, chat_users, i, callback) {
    conn.query('select C2.user_id, C2.nickname as user_name, C2.active, f.uri as user_pic from file_managed f RIGHT JOIN ( ' +
        'select C1.*, u.picture from users u INNER JOIN ' +
        '(select user_id, nickname, active from chat_users where chat_id = ' + chat_ids[i].chat_id + ') C1 ' +
        'ON C1.user_id = u.uid) C2 ' +
        'ON C2.picture = f.fid;', function (err, result_users) {
        if (err) {
            callback(err, result_users);
            return;
        }
        var id = chat_ids[i].chat_id;
        chat_users[id].user = {};
        for (var j = 0; j < result_users.length; j++) {
            if (result_users[j].user_pic) {
                result_users[j].user_pic = result_users[j].user_pic.replace("public://", config.public);
            } else {
                result_users[j].user_pic = config.defaultUserPicUrl;
            }
            chat_users[id].user[result_users[j].user_id] = {};
            chat_users[id].user[result_users[j].user_id].name = result_users[j].user_name;
            chat_users[id].user[result_users[j].user_id].active = result_users[j].active;
            chat_users[id].user[result_users[j].user_id].pic = result_users[j].user_pic;
        }
        i++;
        if (i < chat_ids.length) {
            _loopGetChatUsersByIds(conn, chat_ids, chat_users, i, callback);
        } else {
            callback(null, chat_users);
        }
    });
}

function _loopGetChatMessagesByIds(conn, chat_ids, chat_messages, i, callback) {
	var id = chat_ids[i].chat_id;
	chat_messages[id].messages = [];
	
	async.waterfall([
		function (cb) {
			try {
				conn.query('select * from chat_messages where chat_id = ' + chat_ids[i].chat_id + ' and timestamp > ' + chat_ids[i].checkpoint, function (err, result) {
					if (err) {
						return cb(new Error('SQL错误！' + err));
					}
					console.log('<<<取得未读消息[条数:' + result.length + '][ChatId: ' + chat_ids[i].chat_id + ']');
					for (var j = 0; j < result.length; j++) {
						var message = {};
						message.message_id = result[j].message_id;
						message.sender_id = result[j].sender_id;
						message.content = result[j].content;
						message.timestamp = result[j].timestamp;
						chat_messages[id].messages.push(message);
					}
					cb(null, result.length);
				});
			} catch (ex) {
				return cb(new Error('程序错误：' + ex));
			}
		},
		function (resultLength, cb) {
			try {
				if (resultLength == 0) {
					conn.query('select * from chat_messages where chat_id = ' + chat_ids[i].chat_id + ' order by timestamp desc limit 5', function (err, result) {
						if (err) {
							return cb(new Error('SQL错误！' + err));
						}
						console.log('<<<取得历史消息[条数:' + result.length + '][ChatId: ' + chat_ids[i].chat_id + ']');
						for (var j = result.length - 1; j >= 0; j--) {
							var message = {};
							message.message_id = result[j].message_id;
							message.sender_id = result[j].sender_id;
							message.content = result[j].content;
							message.timestamp = result[j].timestamp;
							chat_messages[id].messages.push(message);
						}
						cb();
					});
				} else {
					cb();
				}
			} catch (ex) {
				return cb(new Error('程序错误：' + ex));
			}
		},
		function (cb) {
			i++;
			if (i < chat_ids.length) {
				_loopGetChatMessagesByIds(conn, chat_ids, chat_messages, i, callback);
			} else {
				cb();
			}
		}
	], function (err, cbResult) {
		if (err) {
			return callback(err);
		}
		callback(null, chat_messages);
	});
}

function getChatsCommon(socket, chatId, callback) {
    try {
        if (!socket) {
            return callback(new Error('对方不在线，请留言给对方。对方下次登录能看到。'));
        }

        var conn = this.conn;
        var sql = 'select CU.chat_id, C.type, C.timestamp, IFNULL(C.pic_url, \'' + config.defaultGroupChatPicUrl + '\') as pic_url, C.title, CG.chat_group_name, CU.checkpoint from chat_users CU ' +
            'join chat C on CU.chat_id = C.chat_id and C.active = 1 ' +
            'join chat_groups CG on CU.chat_group_id = CG.chat_group_id where CU.user_id = ' + socket.uid + ' and CU.active = 1';

        if (chatId) {
            // for single chat update
            sql += ' and CU.chat_id = ' + chatId;
        }
        conn.query(sql, function (err, result_chats) {
            if (err) {
                return callback(new Error('SQL错误！' + err));
            }
            var sendMsg = {
                uid: socket.uid,
                groups: {},
                chats: {}
            };
            if (result_chats && result_chats.length > 0) {
                for (var i = 0; i < result_chats.length; i++) {
                    var id = result_chats[i].chat_id;
                    sendMsg.chats[id] = {};
                    sendMsg.chats[id].type = result_chats[i].type;
                    sendMsg.chats[id].pic_url = result_chats[i].pic_url;
                    sendMsg.chats[id].title = result_chats[i].title;
                    sendMsg.chats[id].checkpoint = result_chats[i].checkpoint;
                    sendMsg.chats[id].timestamp = result_chats[i].timestamp;
                    // 添加聊天分组
                    if (result_chats[i].chat_group_name in sendMsg.groups) {
                        sendMsg.groups[result_chats[i].chat_group_name].push(result_chats[i].chat_id);
                    } else {
                        sendMsg.groups[result_chats[i].chat_group_name] = [result_chats[i].chat_id];
                    }
                }

                // 找到聊天内容
                _loopGetChatUsersByIds(conn, result_chats, sendMsg.chats, 0, function (err, result_chat_users) {
                    if (err) {
                        return callback(new Error('错误！loop_get_chat_users_by_ids：' + err));
                    }
                    sendMsg.chats = result_chat_users;
                    _loopGetChatMessagesByIds(conn, result_chats, sendMsg.chats, 0, function (err, result_chat_messages) {
                        if (err) {
                            return callback(new Error('错误！loop_get_chat_users_by_ids：' + err));
                        }
                        sendMsg.chats = result_chat_messages;
                        if (chatId) {
                            socket.emit('chat', sendMsg);
                        } else {
                            socket.emit('chats', sendMsg);
                        }
                        callback();
                    });
                });
            } else {
                if (chatId) {
                    socket.emit('chat', sendMsg);
                } else {
                    socket.emit('chats', sendMsg);
                }
            }
        });
    } catch (ex) {
        return callback(new Error('程序错误:' + ex));
    }
}

function checkUserValid(socket, ssid, callback) {
    try {
        var conn = this.conn;
        conn.query('SELECT u.*, s.* FROM users u INNER JOIN sessions s ON u.uid = s.uid WHERE s.sid = "' + ssid + '"', function (err, rows) {
            if (err) {
                return callback(new Error('SQL错误！' + err));
            }
            if (!rows || rows.length == 0) {
                return callback(new Error('用户session不存在'));
            }
            console.log('登录用户信息：' + rows[0].name + '[' + rows[0].uid + ']');

            if (onlineUsers[rows[0].uid]) {
                console.log('已登录账户退出'+rows[0].uid);
                sendDirectError(onlineUsers[rows[0].uid], 10001, '用户在另一个终端登录[' + rows[0].uid + ']');
            }
            socket.uid = rows[0].uid;
            onlineUsers[rows[0].uid] = socket;
            socket.emit('ready', null);

            callback();
        });
    } catch (ex) {
        return callback(new Error('程序错误:' + ex));
    }
}

function disconnect(socket, callback) {
    try {
        var conn = this.conn;
        //将退出的用户从在线列表中删除
        if (onlineUsers[socket.uid] == socket) {
            //删除
            delete onlineUsers[socket.uid];
            //在线人数-1
            onlineCount--;

            console.log('>>>下线[Uid：' + socket.uid + ']');
            callback();
        }
    } catch (ex) {
        console.log('用户登出错误');
        return;
    }
}

function startFileUpload(stream, data, callback) {
    try {
        var filename = path.basename(data.name);
        console.log('<<<文件上传' + '[FileName：' + filename + '][FileSize：' + data.size + ']');

        // 创建文件夹，如果不存在
        var targetFolder = config.private + data.chat_id;
        if (!fs.existsSync(targetFolder)) {
            fs.mkdirSync(targetFolder);
        }

        var newFileName = Date.now() + '_' + filename;
        stream.pipe(fs.createWriteStream(targetFolder + '/' + newFileName));
        var fileLink = config.download + '?chat_id=' + data.chat_id + '&file_id=' + newFileName;
        console.log('>>>文件上传' + '[FileName：' + filename + '][FileSize：' + data.size + ']');

        return fileLink;
    } catch (ex) {
        return callback(ex);
    }
}

function finishFileUpload(socket, data, fileLink, fn, callback) {
    try {
        var conn = this.conn;
        console.log('>>>文件传输结束处理[From:' + socket.uid + '][ChatId:' + data.chat_id + ']');
        var timestamp = new Date().getTime();

        async.waterfall([
            function (callback) {
                try {
                    //更新chat时间戳
                    conn.query('UPDATE chat SET timestamp = ' + timestamp + ' WHERE chat_id = ' + data.chat_id, function (err, result) {
                        if (err) {
                            return callback(new Error('SQL错误-chat时间戳无法更新' + err));
                        }
                        callback(null);
                    });
                } catch (ex) {
                    return callback(new Error('程序错误：' + ex));
                }
            },
            function (callback) {
                try {
                    // 更新发送者的checkpoint
                    conn.query('UPDATE chat_users SET checkpoint = ' + timestamp + ' WHERE user_id =  ' + socket.uid + ' AND chat_id = ' + data.chat_id, function (err, result) {
                        if (err) {
                            return callback(new Error('SQL错误-更新checkpoint失败！' + err));
                        }
                        callback();
                    });
                } catch (ex) {
                    return callback(new Error('程序错误：' + ex));
                }
            },
            function (callback) {
                try {
                    conn.query('INSERT INTO chat_messages (chat_id, sender_id, content, timestamp) VALUES (' + data.chat_id + ',' + data.uid + ',"' + fileLink + '",' + timestamp + ')', function (err, result) {
                        if (err) {
                            return callback(new Error('SQL错误-文件链接无法插入' + err));
                        }
                        callback(null, result.insertId);
                    });
                } catch (ex) {
                    return callback(new Error('程序错误：' + ex));
                }
            },
            function (insertId, callback) {
                try {
                    conn.query('SELECT user_id FROM chat_users WHERE chat_id = "' + data.chat_id + '" and active = 1 and user_id != "' + data.uid + '"', function (err, user_ids) {
                        try {
                            if (err) {
                                return callback(new Error('SQL错误-查询聊天组用户列表！' + err));
                            }
                            var sendMsg = {
                                message_id: insertId,
                                chat_id: data.chat_id,
                                sender_id: data.uid,
                                content: fileLink,
                                timestamp: timestamp
                            };
                            for (var i in user_ids) {
                                if (onlineUsers[user_ids[i].user_id]) {
                                    onlineUsers[user_ids[i].user_id].emit('message', sendMsg);
                                }
                            }

                            var cbResult = {};
                            cbResult.success = true;
                            cbResult.message_id = insertId;
                            cbResult.timestamp = timestamp;
                            cbResult.content = fileLink;
                            fn(cbResult);
                            console.log('<<<文件传输结束处理[From:' + socket.uid + '][ChatId:' + data.chat_id + '][MsgId:' + insertId + ']');

                            callback();
                        } catch (ex) {
                            return callback(new Error('程序错误：' + ex));
                        }
                    });
                } catch (ex) {
                    return callback(new Error('程序错误：' + ex));
                }
            }
        ], function (err, cbResult) {
            if (err) {
                return callback(err);
            }
        });
    } catch (ex) {
        return callback(ex);
    }
}

function receiveMessage(socket, recv, fn, callback) {
    try {
        var conn = this.conn;
        var timestamp = new Date().getTime();
        console.log('>>>接收新消息[From:' + socket.uid + '][ChatId:' + recv.chat_id + ']');
        var extraMessage = null;

        async.waterfall([
            function (callback) {
                try {
                    conn.query('UPDATE chat SET timestamp = ' + timestamp + ' WHERE chat_id = ' + recv.chat_id, function (err, result) {
                        if (err) {
                            return callback(new Error('SQL错误-chat时间戳无法更新' + err));
                        }
                        callback(null);
                    });
                } catch (ex) {
                    return callback(new Error('程序错误：' + ex));
                }
            },
            function (callback) {
                console.log('RecvMsg1');
                conn.query('INSERT INTO chat_messages (chat_id, sender_id, content, timestamp) VALUES (' + recv.chat_id + ',' + socket.uid + ',"' + recv.content + '",' + timestamp + ')', function (err, result) {
                    if (err) {
                        return callback(new Error('SQL错误-聊天记录无法插入！' + err))
                    }
                    callback(null, result.insertId);
                });
            },
            function (insertId, callback) {
                console.log('RecvMsg2');
                conn.query('SELECT user_id FROM chat_users WHERE chat_id = "' + recv.chat_id + '" and active = 1 and user_id != "' + socket.uid + '"', function (err, user_ids) {
                    if (err) {
                        return callback(new Error('SQL错误-查询聊天组用户列表！' + err))
                    }
                    if (user_ids.length == 0 && recv.chat_type == config.chatTypeIndividual) {
                        // 单聊找不到对方id，已被删除好友
                        return callback(new Error('和对方不是好友关系'));
                    }
                    var sendMsg = {
                        message_id: insertId,
                        chat_id: recv.chat_id,
                        sender_id: socket.uid,
                        content: recv.content,
                        timestamp: timestamp
                    };
                    var hasReceiver = false;
                    for (var i in user_ids) {
                        if (onlineUsers[user_ids[i].user_id]) {
                            onlineUsers[user_ids[i].user_id].emit('message', sendMsg);
                            console.log('>>>接收新消息[From:' + socket.uid + '][ChatId:' + recv.chat_id + '][To:' + socket.uid + ']');
                            hasReceiver = true;
                        }
                    }
                    if (!hasReceiver && recv.chat_type == config.chatTypeMix) {
                        // 客服聊天，找不到任何在线接收者，表明一方下线，通知另一方仍然可以离线留言。
                        extraMessage = '[自动回复]您好，我现在有事不在，一会再和您联系。';
                    }

                    callback(null, insertId);
                });
            },
            function (insertId, callback) {
                console.log('RecvMsg3');
                // 更新发送者的checkpoint
                conn.query('UPDATE chat_users SET checkpoint = ' + timestamp + ' WHERE user_id =  ' + socket.uid + ' AND chat_id = ' + recv.chat_id, function (err, result) {
                    if (err) {
                        return callback(new Error('SQL错误-更新checkpoint失败！' + err));
                    }

                    if (!!fn) {
                        var cbResult = {};
                        cbResult.success = true;
                        cbResult.message_id = insertId;
                        cbResult.timestamp = timestamp;
                        cbResult.extra_message = extraMessage;
                        fn(cbResult);
                    }
                    console.log('<<<接收新消息[From:' + socket.uid + '][MsgId:' + insertId + ']');

                    callback();
                });
            }
        ], function (err, result) {
            if (err) {
                return callback(err);
            }
            console.log('RecvMsg4');
            return callback();
        });
    } catch (ex) {
        return callback(ex);
    }
}

function getHistoryMessages(recv, fn, callback) {
    try {
        var conn = this.conn;
        var sql = null;
        console.log('>>>获取历史消息[ChatId:' + recv.chat_id + '][MessageId:' + recv.message_id + ']');
        if (recv.message_id != 0) {
            sql = 'SELECT message_id, sender_id, content, timestamp FROM chat_messages WHERE chat_id = ' + recv.chat_id + ' AND message_id < ' + recv.message_id + ' order by message_id desc limit 10';
        } else {
            sql = 'SELECT message_id, sender_id, content, timestamp FROM chat_messages WHERE chat_id = ' + recv.chat_id + ' order by message_id desc limit 10';
        }

        conn.query(sql, function (err, result_chats) {
            if (err) {
                return callback(new Error('SQL错误-聊天记录无法查询！' + err));
            }
            var historyMessages = [];
            if (result_chats) {
                for (var i = 0; i < result_chats.length; i++) {
                    var sendMsg = {
                        message_id: result_chats[i].message_id,
                        sender_id: result_chats[i].sender_id,
                        content: result_chats[i].content,
                        timestamp: result_chats[i].timestamp
                    };
                    historyMessages.push(sendMsg);
                }
                var cbResult = {};
                cbResult.success = true;
                cbResult.historyMessages = historyMessages;
                fn(cbResult);
                console.log('<<<获取历史消息[ChatId:' + recv.chat_id + '][MessageId:' + recv.message_id + ']');

                callback();
            }
        });
    } catch (ex) {
        return callback(ex);
    }
}

function updateCheckpoint(recv, fn, callback) {
    try {
        var conn = this.conn;
        console.log('>>>更新checkpoint[UserId:' + recv.user_id + '][ChatId:' + recv.chat_id + ']');
        var timestamp = new Date().getTime();
        conn.query('UPDATE chat_users SET checkpoint = ' + timestamp + ' WHERE user_id =  ' + recv.user_id + ' AND chat_id = ' + recv.chat_id, function (err, result) {
            if (err) {
                return callback(new Error('SQL错误-更新checkpoint失败!' + err));
            }
            var cbResult = {};
            cbResult.success = true;
            fn(cbResult);
            console.log('<<<更新checkpoint[UserId:' + recv.user_id + '][ChatId:' + recv.chat_id + ']');

            callback();
        });
    } catch (ex) {
        return callback(ex);
    }
}

/* 用于需要事务处理的DB处理 */
function finishDBTransaction(conn, funcName, callback) {
    return function (err, result) {
        if (err) {
            conn.rollback(function () {
                console.log('<<<事务-逻辑失败:' + funcName);
            });
            return callback(err);
        }
        conn.commit(function (err) {
            if (err) {
                conn.rollback(function () {
                    console.log('<<<事务-提交回退:' + funcName);
                });
                return callback(new Error('事务-提交失败' + err));
            }
            console.log('<<<事务-结束:' + funcName);
            conn.release();
            return callback();
        });
    };
}

/* 用于不需要事务处理的DB处理 */
function finishDBQuery(conn, funcName, callback) {
    return function (err, result) {
        if (err) {
            console.log('<<<查询-逻辑失败:' + funcName);
            return callback(err);
        }
        console.log('<<<查询-结束:' + funcName);
        conn.release();
        return callback();
    };
}

function callFunctionCommon(func, args, useTransaction, callback) {
    //var conn = mysql.createConnection(config.mysql);
    //conn.connect(function (err) {
    pool.getConnection(function (err, conn) {
        if (err) {
            return callback(new Error('连接数据库失败'));
        }
        console.log('>>>数据库连接确立:' + func.name);
        if (useTransaction) {
            conn.beginTransaction(function (err) {
                if (err) {
                    return callback(new Error('事务-启动失败'));
                }
                console.log('>>>事务-开始:' + func.name);
                func.apply({conn: conn}, args.concat(finishDBTransaction(conn, func.name, callback)));
            });
        } else {
            console.log('>>>查询-开始:' + func.name);
            func.apply({conn: conn}, args.concat(finishDBQuery(conn, func.name, callback)));
        }
    });
}

io.on('connection', function (socket) {
    var cookies = parseCookies(socket.handshake.headers.cookie);
    if (!cookies || !cookies[config.ssid]) {
        console.log('非法用户登录');
        return sendDirectError(socket, 10000, '非法用户登录');
    }

    async.waterfall([
        // 1.检验是否为注册用户
        function (callback) {
            callFunctionCommon(checkUserValid, [socket, cookies[config.ssid]], false, function (err, result) {
                if (err) {
                    return callback(err);
                }
                callback();
            });
        },
        // 2.开启事件监听
        function (callback) {
            // 2-1.返回客户端结构列表
            socket.on('chats', function (recv, fn) {
                callFunctionCommon(getChatsCommon, [socket, null, function (err, result) {
                    if (err) {
                        return callback(new Error('获取对话列表失败' + err));
                    }
                }], false, function (err, result) {
                    if (err) return sendErrorMsgCommon(fn, err);
                });
            });

            if (config.appName == "nonghua") {
                // 2-2.返回客户端结构列表
                socket.on('consult', function (recv, fn) {
                    callFunctionCommon(assignAgentCommon, [socket, config.consult, config.consultOffset], false, function (err, result) {
                        if (err) return sendErrorMsgCommon(fn, err);
                    });
                });

                // 2-3.分配投诉客服
                socket.on('complain', function (recv, fn) {
                    callFunctionCommon(assignAgentCommon, [socket, config.complain, config.complainOffset], false, function (err, result) {
                        if (err) return sendErrorMsgCommon(fn, err);
                    });
                });
            }

            if (config.appName == "expert") {
                socket.on('customer', function (recv, fn) {
                    callFunctionCommon(assignAgentCustomer, [socket, recv], false, function (err, result) {
                        if (err) return sendErrorMsgCommon(fn, err);
                    });
                });
            }
            // 2-4.监听用户退出
            socket.on('disconnect', function () {
                callFunctionCommon(disconnect, [socket], false, function (err, result) {
                    if (err) return sendErrorMsgCommon(null, err);
                });
            });


            // 2-5.监听文件上传
            ss(socket).on('file', function (stream, data, fn) {
                var fileLink = startFileUpload(stream, data, function (err, result) {
                    if (err) return sendErrorMsgCommon(fn, err);
                });

                stream.on('end', function () {
                    callFunctionCommon(finishFileUpload, [socket, data, fileLink, fn], false, function (err, result) {
                        if (err) return sendErrorMsgCommon(fn, err);
                    });
                });
            });

            // 2-6.监听消息
            socket.on('message', function (recv, fn) {
                callFunctionCommon(receiveMessage, [socket, recv, fn], false, function (err, result) {
                    if (err) return sendErrorMsgCommon(fn, err);
                });
            });


            // 2-7.监听历史消息请求
            socket.on('historyMessages', function (recv, fn) {
                callFunctionCommon(getHistoryMessages, [recv, fn], false, function (err, result) {
                    if (err) return sendErrorMsgCommon(fn, err);
                });
            });


            // 2-8.更新checkpoint
            socket.on('updateCheckpoint', function (recv, fn) {
                callFunctionCommon(updateCheckpoint, [recv, fn], false, function (err, result) {
                    if (err) return sendErrorMsgCommon(fn, err);
                });
            });

            callback();
        }
    ], function (err) {
        if (err) {
            console.log('服务启动失败' + err);
        } else {
            console.log('服务启动成功');
        }
    });
});


function sendDirectError(socket, error_no, msg) {
    console.log(msg);
    socket.emit('errors', {'error_no': error_no, 'message': msg});
    socket.disconnect();
}

function sendErrorMsgCommon(fn, err) {
    if (fn) {
        console.log('错误回送：' + err);
        var cbResult = {};
        cbResult.success = false;
        cbResult.reason = err.message;
        fn(cbResult);
    }
}

/* function generateFailureMessage(reason, fn)
 {
 var cbResult = {};
 cbResult.success = false;
 cbResult.reason = reason;
 fn(cbResult);
 } */

/*
 消息发送回调函数:
 case : true
 cbResult : {
 success : true		  		// show if succeeded or failed
 message_id : int				// message id inserted
 timestamp : timestamp			// when message was inserted
 }


 通用回调格式：
 case : true
 cbResult : {
 success : true  				// show if succeeded or failed
 }


 case : false
 cbResult : {
 success : false  				// show if succeeded or failed
 reason : string				// why message was not sent
 }

 */
